perm filename TCALC.OLD[OLD,HE] blob
sn#501006 filedate 1980-03-24 generic text, type C, neo UTF8
COMMENT ⊗ VALID 00021 PAGES
C REC PAGE DESCRIPTION
C00001 00001
C00003 00002 IFCR ¬DECLARATION(EXTENDED_COMPILATION)
C00004 00003 ! Load module requirements, declarations
C00006 00004 ! SPLANVAL, VPLANVAL, PPLANVAL
C00009 00005 ! Small utilities: PLACESOL, DEVBITS
C00015 00006 ! TRJCLC
C00025 00007 ! Segment time calculators: DEPTIME, RUNTIME
C00031 00008 ! Matrix solvers: DECOMPOSE, SOLVE
C00037 00009 ! POLY, the polynomial spliner: The A matrix
C00044 00010 ! POLY continued: The B vectors
C00048 00011 ! Main body of TRJCLC starts here
C00051 00012 ! Initialize the first node of the motion
C00055 00013 ! Put intermediate points into the thread
C00061 00014 ! Treat the approach
C00068 00015 ! Check for overall time constraints. Fulfil them if possible
C00073 00016 ! Call the polynomial generator on chunks of the motion.
C00079 00017 ! Compute the gravity and inertia terms
C00081 00018 ! Output the motion table
C00092 00019 ! Reclaim all the arrays in the motion thread
C00093 00020 ! CENTCLC, STOPCLC
C00096 00021 ! Bugs
C00097 ENDMK
C⊗;
IFCR ¬DECLARATION(EXTENDED_COMPILATION)
THENC
ENTRY;
BEGIN "tcalc"
IFCR ¬DECLARATION(CREFFING) THENC DEFINE CREFFING = "FALSE"; ENDC
IFCR ¬ CREFFING THENC
COMMENT: Source file requirements;
REQUIRE "ABBREV.SAI[AL,HE]" SOURCE_FILE;
REQUIRE "MACROS.SAI[AL,HE]" SOURCE_FILE;
REQUIRE "RECAUX.HDR[AL,HE]" SOURCE_FILE;
REQUIRE "ARITH.HDR[AL,HE]" SOURCE_FILE ;
REQUIRE "ALREC.SAI[AL,HE]" SOURCE_FILE ;
REQUIRE "INTDEF.SAI[AL,HE]" SOURCE_FILE;
ENDC
REQUIRE "EMITER.HDR[AL,HE]" SOURCE_FILE;
REDEFINE $$PRGID "[]" = ["TCALC"];
ENDC
! Load module requirements, declarations;
REQUIRE "ARMSOL.REL[AL,HE]" LOAD_MODULE;
EXTERNAL SAFE REAL ARRAY LOSTOP, HISTOP, TIMFAC[0:1,1:7];
! First word of array [1:14]. LOSTOP and HISTOP are joint limits,
and TIMFAC is the time in jiffies needed to move one degree (or
inch);
EXTERNAL INTEGER PROCEDURE ARMSOL(REAL ARRAY ANGLE;
RPTR(FRAME,TRANS,SVAL) PTR;INTEGER MECH);
DEFINE DEBUG = "FALSE";
DEFINE YARM_MECH = "'1";
DEFINE YHAND_MECH = "'2";
DEFINE BARM_MECH = "'4";
DEFINE BHAND_MECH = "'10";
DEFINE AHAND_MECH = "'12";
DEFINE ANARM_MECH = "'5";
DEFINE VISE_MECH = "'20";
DEFINE DRIVER_MECH = "'40";
DEFINE YARMSB = "'176000";
DEFINE YHANDSB = "'1000";
DEFINE BARMSB = "'770";
DEFINE BHANDSB = "'4";
DEFINE VISESB = "'2";
DEFINE DRIVERSB = "'1";
! SPLANVAL, VPLANVAL, PPLANVAL;
RPTR(SVAL) PROCEDURE SPLANVAL(RANY OFTHIS);
BEGIN "splanval"
! Returns a scalar as the planning value of this expression;
IF RECTYPE(OFTHIS)=LOC(SVAL)
THEN RETURN(OFTHIS)
ELSE IF RECTYPE(OFTHIS)=LOC(DEXPR)
THEN RETURN(DEXPR:VAL[OFTHIS])
ELSE COMERR("SPLANVAL garbage",OFTHIS);
END "splanval";
RPTR(V3ECT) PROCEDURE VPLANVAL(RANY OFTHIS);
BEGIN "vplanval"
! Returns a vector as the planning value of this expression;
IF RECTYPE(OFTHIS)=LOC(V3ECT)
THEN RETURN(OFTHIS)
ELSE IF RECTYPE(OFTHIS)=LOC(DEXPR)
THEN RETURN(DEXPR:VAL[OFTHIS])
ELSE COMERR("VPLANVAL garbage",OFTHIS);
END "vplanval";
RPTR(VALU$) PROCEDURE PPLANVAL (RANY OFTHIS; REFERENCE BOOLEAN SUCCESS);
BEGIN "pplanval"
! Returns a sval or frame as the planning value of this place expression;
SUCCESS ← TRUE;
IF RECTYPE(OFTHIS)=LOC(SVAL)
THEN RETURN(OFTHIS)
ELSE IF RECTYPE(OFTHIS)=LOC(FRAME)
THEN RETURN(OFTHIS)
ELSE IF RECTYPE(OFTHIS)=LOC(TRANS)
THEN RETURN(OFTHIS)
ELSE IF RECTYPE(OFTHIS)=LOC(DEXPR)
THEN RETURN(DEXPR:VAL[OFTHIS])
ELSE SUCCESS ← FALSE;
RETURN(BPARK); ! The default, to prevent more error messages;
END "pplanval";
! Small utilities: PLACESOL, DEVBITS;
SAFE OWN REAL ARRAY YELLOW,BLUE[1:6];
SIMPLE PROCEDURE INI_POS; ! initialize arm positions;
BEGIN EXTERNAL REAL ARRAY PARK[0:1,1:6];
ARRBLT(YELLOW[1],PARK[0,1],6);ARRBLT(BLUE[1],PARK[1,1],6) END;
REQUIRE INI_POS INITIALIZATION;
PROCEDURE PLACESOL
(REAL ARRAY RES; RPTR(VALU$) PTR; INTEGER MECH; REFERENCE INTEGER FLG);
! PTR points to a frame/trans or a scalar constant.
Its solution is calculated in RES. If armsol has trouble with the
location, then FLG is set TRUE. On the other hand, if it is a
scalar, HANDSOL is called to check bounds (setting FLG) and
to store the result into RES. In any case, MECH specifies
which mechanism (eg BARM) is meant.
;
BEGIN "plcslv"
INTEGER LOJOINT;
LOJOINT ← ARRINFO(RES,1);
IF MECH LAND AHAND_MECH THEN
BEGIN "handsol"
FLG ← ARMSOL(RES,PTR,MECH);
RETURN
END "handsol";
IF MECH = YARM_MECH THEN
BEGIN "yarm"
FLG ← ARMSOL(YELLOW,PTR,YARM_MECH);
ARRBLT(RES[LOJOINT],YELLOW[1],6);
END
ELSE IF MECH = BARM_MECH THEN
BEGIN "barm"
FLG ← ARMSOL(BLUE,PTR,BARM_MECH);
ARRBLT(RES[LOJOINT],BLUE[1],6);
END
ELSE USERERR(0,1,"PLACESOL: unknown device "&CVOS(MECH));
IF FLG THEN PRINT(CRLF & "Joints out of range: ",CVOS(FLG),CRLF);
END "plcslv";
PROCEDURE DEVBITS (REFERENCE INTEGER ARM, SBITS, LOJOINT, HIJOINT; RVAR WHAT);
BEGIN "devbits";
! Takes WHAT as a device name and fills in ARM, SBITS, LOJOINT,
HIJOINT;
IF WHAT = YARM
THEN BEGIN ! Yellow arm;
LOJOINT ← 1;
HIJOINT ← 6;
ARM ← YARM_MECH;
SBITS ← YARMSB;
END
ELSE IF WHAT = YHAND
THEN BEGIN ! Yellow hand;
LOJOINT ← 7;
HIJOINT ← 7;
ARM ← YHAND_MECH;
SBITS ← YHANDSB;
END
ELSE IF WHAT = BARM
THEN BEGIN ! Blue arm;
LOJOINT ← 8;
HIJOINT ← 13;
ARM ← BARM_MECH;
SBITS ← BARMSB;
END
ELSE IF WHAT = BHAND
THEN BEGIN ! Blue hand;
LOJOINT ← 14;
HIJOINT ← 14;
ARM ← BHAND_MECH;
SBITS ← BHANDSB;
END
ELSE IF WHAT = VISE
THEN BEGIN ! Vise;
LOJOINT ← 15;
HIJOINT ← 15;
ARM ← VISE_MECH;
SBITS ← VISESB;
END
ELSE IF WHAT = DRIVER
THEN BEGIN ! Driver;
LOJOINT ← 16;
HIJOINT ← 16;
ARM ← DRIVER_MECH;
SBITS ← DRIVERSB;
END
ELSE BEGIN ! Wrong arm;
COMERR("DEVBITS: No such arm; assuming BLUE.");
LOJOINT ← 8;
HIJOINT ← 13;
ARM ← BARM_MECH;
SBITS ← BARMSB;
END;
END "devbits";
! TRJCLC;
INTERNAL PROCEDURE TRJCLC (RPTR(MOVE$) MOV);
BEGIN "trjclc"
RCLASS TTHREAD (
REAL STIME, UTIME; INTEGER MODE;
RPTR(VARIABLE) EVENT;
RPTR(VARIABLE,VALU$,DEXPR) PLACE;
REAL ARRAY ANGLES, VELS; ! [LOJOINT:HIJOINT];
REAL ARRAY COEFF; ! [1:6,0:5]=[joint,degree] polynomial coefficients;
REAL ARRAY GRAVIN; ! [1:12] gravity, inertia terms for each joint;
RPTR(TTHREAD) NEXT
);
DEFINE TIME_MODE = '3;
DEFINE DEPA_MODE = '4;
DEFINE APPR_MODE = '10;
DEFINE ENDP_MODE = '20;
DEFINE INVI_MODE = '40;
!
Data structures:
A TTHREAD is a linked list of points along which the trajectory
passes. It has these fields:
MODE(INTEGER)
The TIME_MODE bits relate to UTIME:
0:no bound, 1:lower bound, 2:upper bound, 3:exact bound.
DEPA_MODE: on if this point is a departure
APPR_MODE: on if this point is an approach
ENDP_MODE: on if this point is an endpoint (either one)
INVI_MODE: on if this point ends a segment whose time is
inviolate. This applies to the endpoint segments only.
STIME(REAL)
System-calculated time in seconds since previous node. If
there is ia conflict between user and system, then the
resolved time is placed in STIME. That causes problems: The
system time is destroyed, so global resolutions use whatever
foolish thing the user wanted.
UTIME(REAL)
User-supplied time in seconds since previous node.
PLACE(RVAR)
Variable (eventually expression) which has the location of
the mechanism as this node is achieved. This can refer to an
arm (in which case the variable will be a frame) or a hand
(in which case it will be a scalar).
ANGLES(n-VECTOR)
Joint angles for this node, if there is an associated place.
VELS(6-VECTOR)
Joint velocities for this node (deg/sec) if there are some.
EVENT(VARIABLE)
Event to signal to start up code when this node reached.
COEFF(6x6 MATRIX)
The 6 coefficients of the segment ending at this node.
NEXT(PTR(TTHREAD))
Next node.
The trajectory calculator turns motion specifications into
interpretable tables. At the moment it allows any one mechanism,
that is, one arm or one hand. Future work will allow any
combination of mechanisms. The tables are calculated by the
following method:
A thread is made, with a node for each place in the motion
specification, that is, the initial point, the departure, if any,
the via points, the approach point, and the destination. Arm or
hand solutions are calculated for each node. It may be that this
serial calculation will lead to flips of the arm. If this
happens, the proper order is outside-in. This is because the
ARMSOL routine uses the previous solution to resolve ambiguities
in joint 4 of the Scheinman arms.
Any deproach points or calculated via points or calculated
destinations must have code emitted to make a cell for them in
the graph structure. The cell for a departure is marked
permanently invalid. Its calculator uses the hand position
itself, not the place where the arm was to be at the start of the
motion. The cells for the calculated via points and the approach
point are in the graph structure in the usual way. This code
must be emitted at the outermost practical point in the program:
If it is too far in, then it gets redone too often, and if it is
too far out, it might cause graph structure to hang around
associated to non-existent nodes. In any case, it is necessary
to put such code at a block entry, and to be sure to get rid of
the resulting graph structure at block exit. The current code
does not handle any of this.
At this time, the fourth degree polynomials for deproach segments
are calculated, and any given velocity constraints are noted.
The presence of a velocity constraint implies that the
acceleration is constrained to zero. If the user has supplied a
time, it is put in UTIME, and STIME is computed by the system.
If they are compatible, STIME is modified to the final decision
on the time for the segment.
After the entire thread is made, a global check is made to
insure that the timing is in agreement with the user's wishes.
Then the thread is divided into chunks, where each chunk is the
region between two velocity-constrained points (the deproach
points are such). A chunk which has only two points (but not a
deproach chunk, for which the trajectory has already been
calculated) gets a fifth-degree polynomial calculated to match
all the constraints. A chunk with more points requires splining
for the trajectory. The first step is to insert one
unconstrained point in each of the two longest intervals. It has
been found that the best place for these points is almost at one
end of the intervals (.001 of the way to the end) to
minimise overshoot problems. After the fully unconstrained nodes
have been inserted into the thread, the routine POLY is called to
create the coefficients of the third degree splined polynomial.
It has been found that using fourth degree polynomials in two of
the segments instead of inserting two unconstrained points leads
to uncontrollable overshoot. Finally, the resulting trajectory
is emitted.
The following conventions are used for arms and joints. Joints
1-6 are yellow arm (arm 0), and joint 7 is the yellow fingers
(arm 2). Joints 8-13 are the blue arm (arm 1), and joint 14 is
the blue fingers (arm 3). The angle arrays are tailored to have
whatever joints are needed. The arm and hand solution programs
are told which mechanism to expect.
The current code does not check location, velocity or
acceleration bounds except for location bounds at user-specified
places. Instead, location bounds are to a large extent insured
by the servo. Velocity and acceleration can be optimized by
rescaling time, in the cases when the user has not specified any
time in the entire motion, nor any velocities, but this is not
currently attempted.
;
! Segment time calculators: DEPTIME, RUNTIME;
INTERNAL INTEGER SPEED; ! If > 1, motions are slower;
REAL PROCEDURE DEPTIME(REAL ARRAY ENDANG, DEPANG, DEL);
BEGIN "deptim"
! Uses the endpoint and deproach point joint angles and
determines the correct time for the deproach between them,
which it returns. If the deproach point is more than half
the way from the endpoint to the joint limit, it is moved to
the halfway point. This means that DEPANG CAN BE CHANGED BY
THIS PROCEDURE!! This is to prevent deproaches which
overstrain the arm. The resulting DEPANG-ENDANG is stored in
DEL.
;
INTEGER JOINT, ! For loop control;
LOJOINT, HIJOINT; ! Defines which arm;
REAL DELTA, ! For joint angle differences;
TAU, TTAU, ! For times;
TEMP;
LOJOINT ← ARRINFO(ENDANG,1);
HIJOINT ← ARRINFO(ENDANG,2);
! If the distance from ENDANG to DEPANG is more than half
the distance to the joint limit, then modify DEPANG to fall
within such a restriction;
FOR JOINT ← LOJOINT STEP 1 UNTIL HIJOINT DO
BEGIN ! Each iteration checks one joint for stop violation;
DEL[JOINT] ← DELTA ← DEPANG[JOINT] - ENDANG[JOINT];
IF (TEMP ← MEMORY[LOC(HISTOP[0,1])-1+JOINT,REAL]-ENDANG[JOINT]) < 2*DELTA
THEN BEGIN ! Exceed high stop;
DEPANG[JOINT] ← (MEMORY[LOC(HISTOP[0,1])-1+JOINT,REAL]+ENDANG[JOINT])/2.;
DEL[JOINT] ← TEMP / 2.;
END
ELSE IF
(TEMP ← MEMORY[LOC(LOSTOP[0,1])-1+JOINT,REAL]-ENDANG[JOINT]) > 2*DELTA
THEN BEGIN ! Exceed low stop;
DEPANG[JOINT] ← (MEMORY[LOC(LOSTOP[0,1])-1+JOINT,REAL]+ENDANG[JOINT])/2.;
DEL[JOINT] ← TEMP / 2.;
END;
END;
TAU ← TTAU ← 0.;
! Find maximum required time for the motion;
FOR JOINT ← LOJOINT STEP 1 UNTIL HIJOINT DO
BEGIN
TTAU ← MEMORY[LOC(TIMFAC[0,1])-1+JOINT,REAL] * ABS(DEL[JOINT]);
IF TTAU ≥ TAU THEN TAU ← TTAU;
END;
! If you want to use LOU's model of linear accleration,
you should insert here:
IF (TEMP←.4*SQRT(distance moved in cm.)) > TAU THEN TAU ← TEMP;
! This .4 (sec*sqrt(cm)) is based on experience (LOU
1/13/75), it accounts for the distance moved according
to a linear acceleration model;
TAU ← SPEED * TAU; ! Account for speed_factor specification;
RETURN (TAU/60. + .3); ! Add on some slack time,
convert jiffies → seconds;
END "deptim";
REAL PROCEDURE RUNTIME(REAL ARRAY OLDANG, NEWANG);
BEGIN "runtim"
! Uses the old and the new joint angles to determine the
correct time in seconds for one segment of motion;
INTEGER JOINT, ! For loop control;
LOJOINT, HIJOINT; ! Defines which arm;
REAL TAU, TTAU;
TAU ← TTAU ← 0;
LOJOINT ← ARRINFO(OLDANG,1);
HIJOINT ← ARRINFO(OLDANG,2);
FOR JOINT ← LOJOINT STEP 1 UNTIL HIJOINT DO
BEGIN
TTAU ← MEMORY[LOC(TIMFAC[0,1])-1+JOINT,REAL] *
ABS(OLDANG[JOINT]-NEWANG[JOINT]);
IF TTAU ≥ TAU THEN TAU ← TTAU;
END;
TAU ← SPEED * TAU; ! Account for speed_factor specification;
! Add on some slack time and convert jiffies → seconds;
IF LOJOINT=HIJOINT ! hand;
THEN RETURN(TAU/60.0 + 0.3) ! Opening fingers;
! IF NEWANG[LOJOINT] > OLDANG[LOJOINT] ∧ SPEED < 2 ! opening or closing?;
! ELSE RETURN(TAU/60.0 + 0.2) ! Closing fingers is faster;
ELSE RETURN (TAU/60.0 + 0.2);
! RETURN ((TAU/60.0 + 0.4) MAX .6); ! Used to be just this;
END "runtim";
! Matrix solvers: DECOMPOSE, SOLVE;
SAFE OWN INTEGER ARRAY PS[1:50];
PROCEDURE DECOMPOSE(INTEGER N;SAFE REAL ARRAY A,LU);
! Both A and LU are [1:N, 1:N]. Uses global array PS.
Computes triangular matrices L and U and permutation matrix
PS so that LU=PA. Stores (L-I) and U both in LU. The call
DECOMPOSE(N,A,A) will overwrite A with LU.
;
BEGIN "decompose"
INTEGER I, J, K, PIVOTINDEX;
REAL NORMROW, PIVOT, SIZE, BIGGEST, MULT;
SAFE OWN REAL ARRAY R[1:50];
SIMPLE PROCEDURE ILOOP(INTEGER UL;REFERENCE REAL R1,R2);
! Machine-coded for efficiency;
START_CODE
LABEL LP,EU;
MOVE 1,-1('17);
MOVE 2,-2('17);
MOVE 3,-3('17);
SUB 3,K;
JUMPLE 3,EU;
LP: AOJ 1,;
AOJ 2,;
MOVN 4,MULT;
FMPR 4,(1);
FADRM 4,(2);
SOJG 3,LP;
EU: END;
IF N > 50
THEN COMERR("DECOMPOSE can't handle a matrix as large as" & CVS(N),RNULL);
! Initialize PS,LU and R;
FOR I←1 STEP 1 UNTIL N DO
BEGIN
PS[I]←I;
NORMROW←0;
FOR J←1 STEP 1 UNTIL N DO
BEGIN
LU[I,J]←A[I,J];
IF (NORMROW<ABS(LU[I,J])) THEN NORMROW←ABS(LU[I,J]);
END;
IF (NORMROW≠0)
THEN R[I]←1/NORMROW
ELSE BEGIN
R[I]←0;
COMERR("Zero row in DECOMPOSE",RNULL);
END;
END;
! Gaussian elimination with partial pivoting;
FOR K←1 STEP 1 UNTIL N-1 DO
BEGIN "kloop";
BIGGEST ← 0;
FOR I ← K STEP 1 UNTIL N DO
BEGIN
SIZE←ABS(LU[PS[I],K])*R[PS[I]];
IF (BIGGEST<SIZE)
THEN BEGIN
BIGGEST←SIZE;
PIVOTINDEX←I;
END;
END;
IF BIGGEST = 0
THEN BEGIN
COMERR("Singular matrix in DECOMPOSE",RNULL);
DONE "kloop";
END;
IF PIVOTINDEX ≠ K
THEN BEGIN
J←PS[K];
PS[K]←PS[PIVOTINDEX];
PS[PIVOTINDEX]←J;
END;
PIVOT←LU[PS[K],K];
FOR I←K+1 STEP 1 UNTIL N DO
BEGIN
LU[PS[I],K]←MULT←(LU[PS[I],K]/PIVOT);
IF MULT ≠ 0
THEN ILOOP(N,LU[PS[I],K],LU[PS[K],K]);
! The following is the result of the machine code:
FOR J ← K+1 STEP 1 UNTIL N DO
LU[PS[I],J]←LU[PS[I],J]-MULT*LU[PS[K],J];
END;
END "kloop";
IF (LU[PS[N],N]=0)
THEN COMERR("Singular matrix in DECOMPOSE",RNULL);
END "decompose";
SIMPLE PROCEDURE SOLVE(INTEGER N;SAFE REAL ARRAY LU,B,X);
! Arrays LU[1:N,1:N], B,X[1:N]. Uses global safe integer
array PS. Solves AX=B using LU from DECOMPOSE.
;
BEGIN "solve"
INTEGER I,J;
REAL DOT;
SIMPLE PROCEDURE ILOOP(INTEGER LL,UL;REFERENCE REAL R1,R2);
! Machine-coded for efficiency;
START_CODE
LABEL LP,EU;
MOVE 1,-1('17);
MOVE 2,-2('17);
MOVE 3,-3('17);
SUB 3,-4('17);
SETZ 4,;
JUMPL 3,EU;
LP: MOVE 5,(1);
FMPR 5,(2);
FADR 4,5;
AOJ 1,;
AOJ 2,;
SOJGE 3,LP;
EU: MOVEM 4,DOT;
END;
FOR I ← 1 STEP 1 UNTIL N DO
BEGIN
ILOOP(1,I-1,LU[PS[I],1],X[1]);
! Has this effect:
DOT←0
FOR J←1 STEP 1 UNTIL I-1 DO
DOT←DOT+LU[PS[I],J]*X[J];
X[I]←B[PS[I]]-DOT;
END;
X[N] ← X[N] / LU[PS[N],N];
FOR I ← N-1 STEP -1 UNTIL 1 DO
BEGIN ! RF: I changed loop upper index from N, to avoid
subscript errors;
ILOOP(I+1,N,LU[PS[I],I+1],X[I+1]);
! Has this effect:
DOT←0
FOR J←I+1 STEP 1 UNTIL N DO
DOT←DOT+LU[PS[I],J]*X[J];
X[I]←(X[I]-DOT)/LU[PS[I],I];
END;
END "solve";
! POLY, the polynomial spliner: The A matrix;
PROCEDURE POLY (RPTR(TTHREAD) FIRST, LAST; INTEGER LOJ, HIJ, NS);
! Calculate a trajectory for joints LOJ through HIJ using
the thread from FIRST to LAST. The number of segments in the
chunk is given by NS. The location for each node is to be
found in TTHREAD:ANGLES[*][JOINT], except for the
unconstrained points, which are distinguishable in that
TTHREAD:PLACE[*] = RNULL. The velocities of the first and
last points are given in TTHREAD:VELS[*][JOINT]. It is assumed
that the accelerations at these points are to be zero. The
timing for each segment is found in TTHREAD:STIME[*] in the
node at the end of the segment. The coefficients of the
resulting polynomial will be stored in the thread nodes, as
TTHREAD:COEFFS[*][JOINT,degree]. ;
BEGIN "poly"
DEFINE MEM(ARG) "<>" = <MEMORY[ARG,REAL]>;
SAFE REAL ARRAY A [1:4*NS,1:4*NS];
SAFE REAL ARRAY B, X [1:4*NS];
! A is a large matrix and B is a vector, for which we will
solve AX=B. A is the same for each joint, but B is
calculated anew for each joint. Thus only one call to
DECOMPOSE is needed;
RPTR(TTHREAD) P, Q; ! Used in tracking down the motion thread;
INTEGER ROW, COL, N, SEG, ALOC, I, JOINT; ! ALOC is used to point into A;
REAL TEMP;
ARRCLR(A);
N ← 4 * NS;
ROW ← COL ← 1;
! Compute the A matrix decomposition:;
! Fill the A matrix for the first segment;
ALOC ← LOCATION(A[1,1]);
A[ROW,COL] ← A[ROW+1,COL+1] ← 1.;
A[ROW+2,COL+2] ← 2.;
ROW ← ROW + 3;
COL ← COL + 4;
ALOC ← ALOC + 3*N + 4;
Q ← TTHREAD:NEXT[FIRST];
P ← TTHREAD:NEXT[Q];
FOR SEG ← 2 STEP 1 UNTIL NS DO
BEGIN "asegpol"
! Look at segment twixt Q and P;
IF TTHREAD:PLACE[Q] = RNULL
THEN BEGIN "auncnst" ! Left of segment is unconstrained;
MEM(ALOC) ← 1.;
MEM(ALOC-4) ← MEM(ALOC-3) ← MEM(ALOC-2) ← MEM(ALOC-1) ←
-1.;
ALOC ← ALOC + N;
MEM(ALOC-3) ← -1.;
MEM(ALOC-2) ← -2.;
MEM(ALOC-1) ← -3.;
MEM(ALOC+1) ← TEMP ← TTHREAD:STIME[Q]/TTHREAD:STIME[P];
ALOC ← ALOC + N;
MEM(ALOC-2) ← -1.;
MEM(ALOC-1) ← -3.;
MEM(ALOC+2) ← TEMP*TEMP;
! Same effect as:
A[ROW,COL] ← 1.
A[ROW,COL-1] ← A[ROW,COL-2] ← A[ROW,COL-3]
← A[ROW,COL-4] ← A[ROW+1,COL-3]
← A[ROW+2,COL-2] ← -1.
A[ROW+1,COL-1] ← A[ROW+2,COL-1] ← -3.
A[ROW+1,COL-2] ← -2.
A[ROW+2,COL+2] ← (A[ROW+1,COL+1] ←
TTHREAD:STIME[Q]/TTHREAD:STIME[P]) ↑ 2;
ROW ← ROW + 3;
COL ← COL + 4;
ALOC ← ALOC + N + 4;
END "auncnst"
ELSE BEGIN "acnst" ! Left of segment is constrained;
MEM(ALOC-1) ← MEM(ALOC-2) ← MEM(ALOC-3) ← MEM(ALOC-4)
← MEM(ALOC+N) ← 1.;
ALOC ← ALOC + N + N;
MEM(ALOC-1) ← -3.;
MEM(ALOC-2) ← -2.;
MEM(ALOC-3) ← -1.;
MEM(ALOC+1) ← TEMP ← TTHREAD:STIME[Q] / TTHREAD:STIME[P];
ALOC ← ALOC + N;
MEM(ALOC-2) ← -1.;
MEM(ALOC-1) ← -3.;
MEM(ALOC+2) ← TEMP*TEMP;
! Equivalent to:
A[ROW,COL-1] ← A[ROW,COL-2]
← A[ROW,COL-3] ← A[ROW,COL-4] ← A[ROW+1,COL] ← 1.
A[ROW+2,COL-3] ← A[ROW+3,COL-2] ← -1.
A[ROW+2,COL-1] ← A[ROW+3,COL-1] ← -3.
A[ROW+2,COL-2] ← -2.
A[ROW+3,COL+2] ← (A[ROW+2,COL+1] ←
TTHREAD:STIME[Q]/TTHREAD:STIME[P]) ↑ 2;
ROW ← ROW + 4;
COL ← COL + 4;
ALOC ← ALOC + N + 4;
END "acnst";
Q ← P;
P ← TTHREAD:NEXT[Q];
END "asegpol";
! Take care of the constraints at the final point;
COL ← COL - 4;
MEM(ALOC-4) ← MEM(ALOC-3) ← MEM(ALOC-2) ← MEM(ALOC-1) ← 1.;
ALOC ← ALOC + N;
MEM(ALOC-3) ← 1.;
MEM(ALOC-2) ← 2.;
MEM(ALOC-1) ← 3.;
ALOC ← ALOC + N;
MEM(ALOC-2) ← 2.;
MEM(ALOC-1) ← 6.;
! Equivalent to:
A[ROW,COL] ← A[ROW,COL+1] ← A[ROW,COL+2] ← A[ROW,COL+3]
← A[ROW+1,COL+1] ← 1.
A[ROW+1,COL+2] ← A[ROW+2,COL+2] ← 2.
A[ROW+1,COL+3] ← 3.
A[ROW+2,COL+3] ← 6.;
ROW ← ROW + 3;
COL ← COL + 4;
IF ROW ≠ COL ∨ ROW ≠ N + 1 THEN COMERR("ERROR IN POLY");
IF DEBUG ! Debug is defined false. Use RAID to remove the
jump around this code if you want to see the matrices;
THEN BEGIN "adebug" ! Print out the matrix A;
INTEGER WIDTH, DIGITS;
OUTSTR(CRLF);
GETFORMAT(WIDTH,DIGITS);
SETFORMAT(5,2);
FOR ROW ← 1 STEP 1 UNTIL N DO
BEGIN
FOR COL ← 1 STEP 1 UNTIL N DO
OUTSTR(CVF(A[ROW,COL]));
OUTSTR(CRLF);
END;
OUTSTR(CRLF);
SETFORMAT(WIDTH,DIGITS);
END "adebug";
DECOMPOSE(N,A,A);
! POLY continued: The B vectors;
! For each joint, calculate B, solve X and stow away;
FOR JOINT ← LOJ STEP 1 UNTIL HIJ DO
BEGIN "bcalc"
ARRCLR(B);
ROW ← 1;
! Fill the B matrix for the first segment;
B[ROW] ← TTHREAD:ANGLES[FIRST][JOINT];
B[ROW+1] ← TTHREAD:STIME[FIRST] * TTHREAD:VELS[FIRST][JOINT];
! If we ever put in non-zero acceleration constraints:
B[ROW+2] ← TTHREAD:STIME[FIRST][JOINT]↑2 * TTHREAD:ACCS[FIRST][JOINT];
ROW ← ROW + 3;
Q ← TTHREAD:NEXT[FIRST];
P ← TTHREAD:NEXT[Q];
FOR SEG ← 2 STEP 1 UNTIL NS DO
BEGIN "bsegpol"
! Look at segment twixt Q and P;
IF TTHREAD:PLACE[Q] = RNULL
THEN BEGIN "buncnst" ! Left of segment is unconstrained;
ROW ← ROW + 3;
END "buncnst"
ELSE BEGIN "bcnst" ! Left of segment is constrained;
B[ROW] ← B[ROW+1] ← TTHREAD:ANGLES[Q][JOINT];
ROW ← ROW + 4;
END "bcnst";
Q ← P;
P ← TTHREAD:NEXT[Q];
END "bsegpol";
! Take care of the constraints at the final point;
B[ROW] ← TTHREAD:ANGLES[LAST][JOINT];
B[ROW+1] ← TTHREAD:STIME[LAST] * TTHREAD:VELS[LAST][JOINT];
! If we ever put in non-zero acceleration constraints:
B[ROW+2] ← TTHREAD:STIME[LAST]↑2 * TTHREAD:ACCS[LAST][JOINT];
ROW ← ROW + 3;
IF ROW ≠ N + 1 THEN COMERR("ERROR IN POLY");
IF DEBUG ! Debug is defined false. Use RAID to remove the
jump around this code if you want to see the matrices;
THEN BEGIN "bdebug" ! Print out the matrix B;
INTEGER WIDTH, DIGITS;
OUTSTR(CRLF);
GETFORMAT(WIDTH,DIGITS);
SETFORMAT(5,2);
OUTSTR(CRLF);
FOR ROW ← 1 STEP 1 UNTIL N DO
OUTSTR(CVF(B[ROW]));
OUTSTR(CRLF);
SETFORMAT(WIDTH,DIGITS);
END "bdebug";
SOLVE(N,A,B,X);
! Stow away the answer into the coefficient matrices;
P ← TTHREAD:NEXT[FIRST];
I ← 1;
FOR SEG ← 1 STEP 1 UNTIL NS DO
BEGIN "stow" ! Each iteration stores the coefficients into one node;
ARRBLT(TTHREAD:COEFF[P][JOINT,0],X[I],4);
I ← I + 4;
P ← TTHREAD:NEXT[P];
END "stow";
END "bcalc";
END "poly";
! Main body of TRJCLC starts here;
RPTR(TTHREAD)
MOTION, ! The entire motion will be stored on this thread;
LEADTHREAD, ! A forward pointer used in scanning down thread;
CURTHREAD, OLDTHREAD; ! Used to trace down the motion;
REAL ARRAY DEL [1:14]; ! Joint angle differences;
RANY P, Q, ! Used in tracking down cell links;
TMPPLACE; ! Used as a temp in location calculations;
RPTR(VARIABLE) VAR;
REAL UT, ST, TT; ! User-defined time, system-computed time, total time;
INTEGER M, ! Holds modes;
FLAG, ! Boolean, for success parameters;
JOINT, ! For loop control;
I, ! For loop control;
LOJOINT, HIJOINT, ! Defines which arm;
ARM, ! Mechanism bits for the device used, eg YARM_MECH;
SBITS, ! Status bits for the device used, eg YARMSB;
SEGCNT, ! How many segments to the motion;
LAB, ! For code emission: a label;
DPTR, SEGLEN; ! For code emission: pointers into DATA and RELOC;
INTEGER ARRAY DATA, RELOC [0:1000]; ! Used for emitting code;
DEVBITS(ARM,SBITS,LOJOINT,HIJOINT,MOVE$:CF[MOV]);
SEGCNT ← 0;
TT ← 0.;
! Establish the speed factor for this move;
SPEED ← MOVE$:SFAC[MOV]; ! Bound by WLDMOD;
IF SPEED < 1 THEN
BEGIN "too fast"
COMERR("Speed_factor for MOVE < 1, setting it to 1",MOV);
SPEED ← 1;
END "too fast";
! Initialize the first node of the motion;
MOTION ← NEW_RECORD(TTHREAD);
TTHREAD:PLACE[MOTION] ← MOVE$:CFVAL[MOV]; ! active arm's current frame;
TTHREAD:MODE[MOTION] ← ENDP_MODE; ! endpoint;
NewArray(REAL,TTHREAD:ANGLES[MOTION],[LOJOINT:HIJOINT]);
NewArray(REAL,TTHREAD:VELS[MOTION],[LOJOINT:HIJOINT]);
TMPPLACE ← PPLANVAL(TTHREAD:PLACE[MOTION],FLAG);
IF ¬FLAG THEN COMERR("Illegal start point",MOV);
PLACESOL(TTHREAD:ANGLES[MOTION],TMPPLACE,ARM,FLAG);
IF FLAG THEN
COMERR("The initial location is not accessible.
The closest reasonable point is being used.",MOV);
CURTHREAD ← MOTION;
! Treat the departure;
P ← MOVE$:CLAUSES[MOV];
WHILE (P≠RNULL) ∧ (RECTYPE(CELL:CAR[P])≠LOC(DEPARTURE)) DO P ← CELL:CDR[P];
IF (P ≠ RNULL) ∧ (DEPARTURE:THRU[CELL:CAR[P]] ≠ NILDEPROACH)
THEN BEGIN "depart" ! Won't work for fingers, of course;
IF LOJOINT = HIJOINT THEN COMERR("No deproaches allowed for fingers");
SEGCNT ← SEGCNT + 1;
OLDTHREAD ← CURTHREAD;
CURTHREAD ← TTHREAD:NEXT[OLDTHREAD] ← NEW_RECORD(TTHREAD);
TTHREAD:PLACE[CURTHREAD] ← DEPARTURE:ACTPLACE[CELL:CAR[P]];
NewArray(REAL,TTHREAD:ANGLES[CURTHREAD],[LOJOINT:HIJOINT]);
TMPPLACE ← PPLANVAL(TTHREAD:PLACE[CURTHREAD],FLAG);
IF ¬FLAG THEN COMERR("Illegal departure point",MOV);
PLACESOL(TTHREAD:ANGLES[CURTHREAD],TMPPLACE,ARM,FLAG);
IF FLAG THEN
COMERR("This departure location is not accessible.
The closest reasonable point is being used.",TTHREAD:PLACE[CURTHREAD]);
TTHREAD:STIME[CURTHREAD] ←
DEPTIME(TTHREAD:ANGLES[OLDTHREAD],TTHREAD:ANGLES[CURTHREAD],DEL);
TT ← TT + TTHREAD:STIME[CURTHREAD];
NewArray(REAL,TTHREAD:VELS[CURTHREAD],[LOJOINT:HIJOINT]);
NewArray(REAL,TTHREAD:COEFF[CURTHREAD],[LOJOINT:HIJOINT,0:5]);
FOR JOINT ← LOJOINT STEP 1 UNTIL HIJOINT DO
BEGIN ! stow away the coefficients for this joint.
The poly for each angle is -del*t↑4 + 2*del*t↑3 + initial;
REAL DL;
DL ← DEL[JOINT];
TTHREAD:COEFF[CURTHREAD][JOINT,4] ← -DL;
TTHREAD:COEFF[CURTHREAD][JOINT,3] ← 2. * DL;
TTHREAD:COEFF[CURTHREAD][JOINT,0] ←
TTHREAD:ANGLES[OLDTHREAD][JOINT];
TTHREAD:VELS[CURTHREAD][JOINT] ←
2. * DL / TTHREAD:STIME[CURTHREAD];
END;
TTHREAD:MODE[CURTHREAD] ← DEPA_MODE + INVI_MODE;
END "depart";
! Put intermediate points into the thread;
P ← MOVE$:CLAUSES[MOV];
WHILE TRUE DO
BEGIN "interm"
! This loop is terminated by a DONE. Each iteration looks at
the next via on the clauses list;
RPTR(VIA) VIAP;
WHILE (P≠RNULL) ∧ (RECTYPE(CELL:CAR[P])≠LOC(VIA)) DO
P ← CELL:CDR[P];
IF P = RNULL THEN DONE "interm";
VIAP ← CELL:CAR[P];
SEGCNT ← SEGCNT + 1;
OLDTHREAD ← CURTHREAD;
CURTHREAD ← TTHREAD:NEXT[OLDTHREAD] ← NEW_RECORD(TTHREAD);
IF VIA:CODE[VIAP] ≠ RNULL THEN ! Set up event to signal for code at VIA;
TTHREAD:EVENT[CURTHREAD] ←
IF RECTYPE(VIA:CODE[VIAP])=LOC(CMON)
THEN CMON:CONDITION[VIA:CODE[VIAP]]
ELSE EVDO:VAR[VIA:CODE[VIAP]];
TTHREAD:PLACE[CURTHREAD] ← VIA:ACTPLACE[VIAP];
NewArray(REAL,TTHREAD:ANGLES[CURTHREAD],[LOJOINT:HIJOINT]);
NewArray(REAL,TTHREAD:COEFF[CURTHREAD],[LOJOINT:HIJOINT,0:5]);
TMPPLACE ← PPLANVAL(TTHREAD:PLACE[CURTHREAD],FLAG);
IF ¬FLAG THEN COMERR("Illegal via point",MOV);
PLACESOL(TTHREAD:ANGLES[CURTHREAD],TMPPLACE,ARM,FLAG);
IF FLAG THEN
COMERR("This via location is not accessible.
The closest reasonable point is being used.",
CONS(MOV,CONS(TTHREAD:PLACE[CURTHREAD],RNULL)));
IF VIA:VELOC[VIAP] ≠ RNULL
THEN BEGIN ! There is a velocity specification here;
IF LOJOINT = HIJOINT
THEN TTHREAD:VELS[CURTHREAD][LOJOINT]
← SVAL:VAL[SPLANVAL(VELOCITY:VELOC[VIA:VELOC[VIAP]])]
ELSE BEGIN "fvel"
RPTR(V3ECT) VTEMP,V2TEMP; ! To hold offset vector (inches/second);
RPTR(TRANS) FTEMP; ! To hold frame value;
REAL ARRAY OFFANG [1:6]; ! Offset angles;
INTEGER I; ! Loop control;
NewArray(REAL,TTHREAD:VELS[CURTHREAD],[LOJOINT:HIJOINT]);
FTEMP ← IF RECTYPE(TMPPLACE)=LOC(FRAME) THEN FRAME:VAL[TMPPLACE]
ELSE TMPPLACE;
VTEMP ← VPLANVAL(VELOCITY:VELOC[VIA:VELOC[VIAP]]);
VTEMP ← V3ADD(TRANS:P[FTEMP],SVMUL(0.2,VTEMP)); ! Add in offset vector;
ARRBLT(OFFANG[1],TTHREAD:ANGLES[CURTHREAD][LOJOINT],6);
! So the out-of bounds result will be reasonable;
V2TEMP ← TRANS:P[FTEMP];
TRANS:P[FTEMP] ← VTEMP;
ARMSOL(OFFANG,FTEMP,IF LOJOINT=1 THEN YARM_MECH ELSE BARM_MECH);
TRANS:P[FTEMP] ← V2TEMP;
FOR JOINT ← LOJOINT STEP 1 UNTIL LOJOINT+5 DO
TTHREAD:VELS[CURTHREAD][JOINT] ←
5.*(OFFANG[JOINT-LOJOINT+1]
- TTHREAD:ANGLES[CURTHREAD][JOINT]);
END "fvel";
END;
ST ← TTHREAD:STIME[CURTHREAD] ←
RUNTIME(TTHREAD:ANGLES[OLDTHREAD],TTHREAD:ANGLES[CURTHREAD]);
IF VIA:TIME[VIAP] ≠ RNULL
THEN BEGIN ! The time is constrained;
RPTR(DURATION) DUR;
DUR ← VIA:TIME[VIAP];
UT ← TTHREAD:UTIME[CURTHREAD]
← SVAL:VAL[SPLANVAL(DURATION:TIME[DUR])];
M ← TTHREAD:MODE[CURTHREAD] ← DURATION:TIME_RELN[DUR];
! test for incompatibilites;
IF ST > UT ∧ M ≥ 2 THEN
BEGIN
COMERR(
"Cannot satisfy your time request for this segment without danger;
you want "&CVG(UT)&" seconds, and I think you need "&CVG(ST)&"
seconds. Nonetheless, I am using your request.");
IF UT ≤ 0 THEN
BEGIN
COMERR("But I refuse to let you get away with no time at all!");
UT ← ST;
END;
END;
IF (M=1 ∧ ST<UT) ∨ (M=2 ∧ ST>UT) ∨ (M=3 ∧ ST≠UT) THEN
TTHREAD:STIME[CURTHREAD] ← UT;
END;
TT ← TT + TTHREAD:STIME[CURTHREAD];
P ← CELL:CDR[P];
END "interm";
! Treat the approach;
P ← MOVE$:CLAUSES[MOV];
WHILE (P≠RNULL) ∧ (RECTYPE(CELL:CAR[P])≠LOC(APPROACH)) DO P ← CELL:CDR[P];
IF (P ≠ RNULL) ∧ (APPROACH:THRU[CELL:CAR[P]] ≠ NILDEPROACH)
THEN BEGIN "approa" ! Will not work for finger operation;
IF LOJOINT = HIJOINT THEN COMERR("No deproaches allowed for fingers");
SEGCNT ← SEGCNT + 1;
OLDTHREAD ← CURTHREAD;
CURTHREAD ← TTHREAD:NEXT[OLDTHREAD] ← NEW_RECORD(TTHREAD);
TTHREAD:PLACE[CURTHREAD] ← APPROACH:ACTPLACE[CELL:CAR[P]];
NewArray(REAL,TTHREAD:ANGLES[CURTHREAD],[LOJOINT:HIJOINT]);
TMPPLACE ← PPLANVAL(TTHREAD:PLACE[CURTHREAD],FLAG);
IF ¬FLAG THEN COMERR("Illegal approach point",MOV);
PLACESOL(TTHREAD:ANGLES[CURTHREAD],TMPPLACE,ARM,FLAG);
IF FLAG THEN
COMERR("This approach location is not accessible.
The closest reasonable point is being used.",TTHREAD:PLACE[CURTHREAD]);
TTHREAD:STIME[CURTHREAD] ←
RUNTIME(TTHREAD:ANGLES[OLDTHREAD],TTHREAD:ANGLES[CURTHREAD]);
TT ← TT + TTHREAD:STIME[CURTHREAD];
NewArray(REAL,TTHREAD:VELS[CURTHREAD],[LOJOINT:HIJOINT]);
NewArray(REAL,TTHREAD:COEFF[CURTHREAD],[LOJOINT:HIJOINT,0:5]);
TTHREAD:MODE[CURTHREAD] ← APPR_MODE;
SEGCNT ← SEGCNT + 1;
OLDTHREAD ← CURTHREAD;
CURTHREAD ← TTHREAD:NEXT[OLDTHREAD] ← NEW_RECORD(TTHREAD);
TTHREAD:PLACE[CURTHREAD] ← MOVE$:DEXP[MOV];
TTHREAD:MODE[CURTHREAD] ← ENDP_MODE + INVI_MODE;
NewArray(REAL,TTHREAD:ANGLES[CURTHREAD],[LOJOINT:HIJOINT]);
TMPPLACE ← PPLANVAL(TTHREAD:PLACE[CURTHREAD],FLAG);
IF ¬FLAG THEN COMERR("Illegal destination point",MOV);
PLACESOL(TTHREAD:ANGLES[CURTHREAD],TMPPLACE,ARM,FLAG);
IF FLAG THEN
COMERR("This destination location is not accessible.
The closest reasonable point is being used.",
CONS(MOV,CONS(TTHREAD:PLACE[CURTHREAD],RNULL)));
TTHREAD:STIME[CURTHREAD] ←
DEPTIME(TTHREAD:ANGLES[CURTHREAD],TTHREAD:ANGLES[OLDTHREAD],DEL);
TT ← TT + TTHREAD:STIME[CURTHREAD];
NewArray(REAL,TTHREAD:VELS[CURTHREAD],[LOJOINT:HIJOINT]);
NewArray(REAL,TTHREAD:COEFF[CURTHREAD],[LOJOINT:HIJOINT,0:5]);
FOR JOINT ← LOJOINT STEP 1 UNTIL HIJOINT DO
BEGIN ! stow away the coefficients for this joint. If
del is (final - initial), then the poly for each angle is
{del*t↑4 - 2*del*t↑3 + 2*del*t + initial}, but note that
DEL holds the negative of del at this point;
REAL DL;
DL ← - DEL[JOINT];
TTHREAD:COEFF[CURTHREAD][JOINT,4] ← DL;
TTHREAD:COEFF[CURTHREAD][JOINT,3] ← -2. * DL;
TTHREAD:COEFF[CURTHREAD][JOINT,1] ← 2. * DL;
TTHREAD:COEFF[CURTHREAD][JOINT,0] ←
TTHREAD:ANGLES[OLDTHREAD][JOINT];
TTHREAD:VELS[OLDTHREAD][JOINT] ←
2. * DL / TTHREAD:STIME[CURTHREAD];
END;
END "approa"
ELSE BEGIN "arrive"
! There is no deproach point, just put in the final point;
SEGCNT ← SEGCNT + 1;
OLDTHREAD ← CURTHREAD;
CURTHREAD ← TTHREAD:NEXT[OLDTHREAD] ← NEW_RECORD(TTHREAD);
TTHREAD:PLACE[CURTHREAD] ← MOVE$:DEXP[MOV];
TTHREAD:MODE[CURTHREAD] ← ENDP_MODE;
NewArray(REAL,TTHREAD:ANGLES[CURTHREAD],[LOJOINT:HIJOINT]);
NewArray(REAL,TTHREAD:VELS[CURTHREAD],[LOJOINT:HIJOINT]);
TMPPLACE ← PPLANVAL(TTHREAD:PLACE[CURTHREAD],FLAG);
IF ¬FLAG THEN COMERR("Illegal destination point",MOV);
PLACESOL(TTHREAD:ANGLES[CURTHREAD],TMPPLACE,ARM,FLAG);
IF FLAG THEN
COMERR("This destination location is not accessible.
The closest reasonable point is being used.",
CONS(MOV,CONS(TTHREAD:PLACE[CURTHREAD],RNULL)));
TTHREAD:STIME[CURTHREAD] ←
RUNTIME(TTHREAD:ANGLES[OLDTHREAD],TTHREAD:ANGLES[CURTHREAD]);
TT ← TT + TTHREAD:STIME[CURTHREAD];
NewArray(REAL,TTHREAD:COEFF[CURTHREAD],[LOJOINT:HIJOINT,0:5]);
END "arrive";
! See if there's an ON ARRIVAL clause;
P ← MOVE$:CLAUSES[MOV];
WHILE (P≠RNULL) ∧ ¬( (RECTYPE(Q←CELL:CAR[P])=LOC(CMON))
∧ (RECTYPE(Q←CMON:CONDITION[Q])=LOC(VARIABLE))
∧ EQU(".AE",VARIABLE:NAME[Q][1 FOR 3]) )
DO P ← CELL:CDR[P];
IF (P ≠ RNULL) THEN TTHREAD:EVENT[CURTHREAD] ← Q;
! Check for overall time constraints. Fulfil them if possible;
P ← MOVE$:CLAUSES[MOV];
WHILE (P≠RNULL) ∧ (RECTYPE(CELL:CAR[P])≠LOC(DURATION)) DO
P ← CELL:CDR[P];
IF P ≠ RNULL
THEN BEGIN "timcst"
! There is a global time constraint. Must try to fulfil it;
REAL AVAIL, CURTIM, FACTOR;
INTEGER M;
UT ← SVAL:VAL[SPLANVAL(DURATION:TIME[CELL:CAR[P]])];
IF UT>TT ∧ (DURATION:TIME_RELN[CELL:CAR[P]] LAND '1) ! (> or =);
THEN BEGIN "stretch" ! Easy case;
AVAIL ← 0.; ! Restricted extra time;
CURTIM ← 0; ! Currently used unrestricted time;
CURTHREAD ← TTHREAD:NEXT[MOTION]; ! First segment has no time;
WHILE CURTHREAD ≠ RNULL DO
BEGIN "timchk"
IF ¬((M←TTHREAD:MODE[CURTHREAD]) LAND INVI_MODE)
THEN ! Time in this segment not inviolate;
IF (M←M LAND '3) ≤ 1
THEN CURTIM ← CURTIM + TTHREAD:STIME[CURTHREAD]
ELSE IF M = 2
THEN AVAIL ← AVAIL + TTHREAD:UTIME[CURTHREAD]
- TTHREAD:STIME[CURTHREAD];
CURTHREAD ← TTHREAD:NEXT[CURTHREAD];
END "timchk";
IF CURTIM
THEN BEGIN "dostretch" ! Just modify those segments
which are not inviolate and have mode ≤ 1;
FACTOR ← (CURTIM + UT - TT) / CURTIM;
CURTHREAD ← TTHREAD:NEXT[MOTION];
WHILE CURTHREAD ≠ RNULL DO
BEGIN ! Expand right segments;
IF ¬((M←TTHREAD:MODE[CURTHREAD]) LAND INVI_MODE)
∧ (M LAND '3) ≤ 1
THEN TTHREAD:STIME[CURTHREAD]
← FACTOR * TTHREAD:STIME[CURTHREAD];
CURTHREAD ← TTHREAD:NEXT[CURTHREAD];
END;
END
ELSE COMERR(
"You want"&CVG(UT)&"seconds for this motion, and I could give you up
to"& CVG(TT+AVAIL) &", but I am only giving you"& CVG(TT)
&"instead.");
END "stretch"
ELSE IF UT<TT ∧ (DURATION:TIME_RELN[CELL:CAR[P]] LAND '3) ≥ 2 ! (< or =);
THEN BEGIN "shrink" ! Tough case;
COMERR(
"You want only" & CVG(UT) & "for this motion, and I think you need
" & CVG(TT) &". In order to satisfy your request, I am disregarding any
other time constraints you may have placed on the motion.");
CURTIM ← 0; ! Currently used non-inviolate time;
CURTHREAD ← TTHREAD:NEXT[MOTION]; ! First segment has no time;
WHILE CURTHREAD ≠ RNULL DO
BEGIN "timcnt"
IF ¬((M←TTHREAD:MODE[CURTHREAD]) LAND INVI_MODE)
THEN ! Time in this segment not inviolate;
CURTIM ← CURTIM + TTHREAD:STIME[CURTHREAD];
CURTHREAD ← TTHREAD:NEXT[CURTHREAD];
END "timcnt";
FACTOR ← (CURTIM + UT - TT) / CURTIM;
IF FACTOR ≤ 0
THEN BEGIN
COMERR(
"Your overall time constraint of" & CVG(UT) & "is ridiculous; I am
ignoring it.");
FACTOR ← 1.;
END;
CURTHREAD ← TTHREAD:NEXT[MOTION];
WHILE CURTHREAD ≠ RNULL DO
BEGIN ! Contract right segments;
IF ¬((M←TTHREAD:MODE[CURTHREAD]) LAND INVI_MODE)
THEN TTHREAD:STIME[CURTHREAD]
← FACTOR * TTHREAD:STIME[CURTHREAD];
CURTHREAD ← TTHREAD:NEXT[CURTHREAD];
END;
END "shrink";
END "timcst";
! Call the polynomial generator on chunks of the motion.
A chunk contains all segments between two velocity-constrained points;
OLDTHREAD ← MOTION;
CURTHREAD ← TTHREAD:NEXT[OLDTHREAD];
WHILE CURTHREAD ≠ RNULL DO
BEGIN "chunk"
! Each iteration finds one chunk, brackets it between
OLDTHREAD and CURTHREAD, and makes polys;
INTEGER PNTCNT; ! Counts number of points in each chunk;
PNTCNT ← 2; ! Count the end nodes this way;
WHILE (MEMLOC(TTHREAD:VELS[CURTHREAD],INTEGER) = 0) DO
BEGIN ! This chunk includes node pointed to by CURTHREAD;
PNTCNT ← PNTCNT + 1;
CURTHREAD ← TTHREAD:NEXT[CURTHREAD];
END;
! Now OLDTHREAD and CURTHREAD point to nodes on each end of
chunk;
IF PNTCNT = 2
THEN IF TTHREAD:MODE[OLDTHREAD]=APPR_MODE ∨
TTHREAD:MODE[CURTHREAD]=DEPA_MODE
THEN ! This is an approach or departure segment, so
the polynomials have already been calculated;
ELSE
! Two-point system. Use the fifth-order polynomial
which will meet the position, velocity, (and
acceleration) constraints. This is faster than the
older version which inserted two equispaced
unconstrained points and solved third degree
polynomials using POLY;
FOR JOINT ← LOJOINT STEP 1 UNTIL HIJOINT DO
BEGIN "oneseg"
REAL P0, V0, A0, P1, V1, A1;
! Initial, final position, vel, acc;
OWN REAL ARRAY COE[0:5]; ! For efficiency;
ST ← TTHREAD:STIME[CURTHREAD];
P0 ← TTHREAD:ANGLES[OLDTHREAD][JOINT];
P1 ← TTHREAD:ANGLES[CURTHREAD][JOINT];
V0 ← TTHREAD:VELS[OLDTHREAD][JOINT]*ST;
V1 ← TTHREAD:VELS[CURTHREAD][JOINT]*ST;
A0 ← 0.0; ! *ST*ST;
A1 ← 0.0; ! *ST*ST;
COE[0] ← P0;
COE[1] ← V0;
COE[2] ← A0 / 2.;
COE[3] ← -(8.*V1 + 12.*V0 + 3.*A0 - A1 - 20.*P1 + 20.*P0) / 2.;
COE[4] ← (14.*V1 + 16.*V0 + 3.*A0 - 2.*A1 - 30.*P1 + 30.*P0) / 2.;
COE[5] ← -(6.*V1 + 6.*V0 + A0 - A1 - 12.*P1 + 12.*P0) / 2.;
ARRBLT(TTHREAD:COEFF[CURTHREAD][JOINT,0],COE[0],6);
END "oneseg"
ELSE BEGIN "sevseg" ! There are several segments. Find
the two with longest intervals, and put free
points there. There is some dispute as to the
best place to put these points. The current
version puts them very close to the beginning of
the interval. An older version put them in the
middle, and this led to massive overshoots;
REAL T1, T2; ! Longest, next longest times;
RPTR(TTHREAD) Q1, Q2; ! Longest, next longest segment starters;
T1 ← T2 ← 0.;
Q ← OLDTHREAD;
P ← TTHREAD:NEXT[Q];
WHILE Q ≠ CURTHREAD DO
BEGIN "max" ! This loop finds the two longest intervals;
IF (ST←TTHREAD:STIME[P]) > T2
THEN IF ST > T1
THEN BEGIN ! New longest;
T2 ← T1;
T1 ← ST;
Q2 ← Q1;
Q1 ← Q;
END
ELSE BEGIN ! New next-longest;
T2 ← ST;
Q2 ← Q;
END;
Q ← P;
P ← TTHREAD:NEXT[P]
END "max";
P ← TTHREAD:NEXT[Q1];
Q ← TTHREAD:NEXT[Q1] ← NEW_RECORD(TTHREAD);
TTHREAD:NEXT[Q] ← P;
TTHREAD:STIME[Q] ← T1 * 0.001;
TTHREAD:STIME[P] ← T1 - TTHREAD:STIME[Q];
NewArray(REAL,TTHREAD:COEFF[Q],[LOJOINT:HIJOINT,0:5]);
P ← TTHREAD:NEXT[Q2];
Q ← TTHREAD:NEXT[Q2] ← NEW_RECORD(TTHREAD);
TTHREAD:NEXT[Q] ← P;
TTHREAD:STIME[Q] ← T2 * 0.001;
TTHREAD:STIME[P] ← T2 - TTHREAD:STIME[Q];
NewArray(REAL,TTHREAD:COEFF[Q],[LOJOINT:HIJOINT,0:5]);
POLY(OLDTHREAD,CURTHREAD,LOJOINT,HIJOINT,PNTCNT+1);
! FOR JOINT ← LOJOINT STEP 1 UNTIL HIJOINT DO
POLY(OLDTHREAD,CURTHREAD,JOINT,PNTCNT+1);
END "sevseg";
OLDTHREAD ← CURTHREAD;
CURTHREAD ← TTHREAD:NEXT[OLDTHREAD];
END "chunk";
! Compute the gravity and inertia terms;
CURTHREAD ← TTHREAD:NEXT[MOTION];
WHILE CURTHREAD ≠ RNULL DO
BEGIN "grav"
REQUIRE "BEJCZY.REL[AL,HE]" LOAD_MODULE;
REQUIRE "FAITRG.REL[AL,HE]" LOAD_MODULE;
EXTERNAL PROCEDURE DTERMS(REFERENCE REAL RES, ANG; INTEGER ARM);
IF MEMLOC(TTHREAD:ANGLES[CURTHREAD],INTEGER) = 0
THEN BEGIN ! Need to set up angles array on this free point;
NewArray(REAL,TTHREAD:ANGLES[CURTHREAD],[LOJOINT:HIJOINT]);
FOR JOINT ← LOJOINT STEP 1 UNTIL HIJOINT DO
TTHREAD:ANGLES[CURTHREAD][JOINT] ← TTHREAD:COEFF[CURTHREAD][JOINT,0];
END;
NewArray(REAL,TTHREAD:GRAVIN[CURTHREAD],[2*LOJOINT:2*HIJOINT+1]);
DTERMS(TTHREAD:GRAVIN[CURTHREAD][2*LOJOINT],
TTHREAD:ANGLES[CURTHREAD][LOJOINT], ARM);
CURTHREAD ← TTHREAD:NEXT[CURTHREAD];
END "grav";
! Output the motion table;
! The format for the coefficients is described in ARM.PAL[11,BES].
SERVO BIT WORD
YARMSB, YHANDSB, BARMSB, or BHANDSB
SERVO BIT WORD
COMMAND BITS; DEFINE NONULLCB = "'1"; ! No end null;
DEFINE WOBBLECB = "'2"; ! Wobble at end;
DEFINE DEPARTCB = "'4"; ! ∃ Departure point;
DEFINE RTMOVECB = "'10"; ! Experimental move
WOBBLE VALUE POINTER Pointer to wobble value for this motion (constant)
RELATIVE SEGMENT PTR Length (bytes) of first segment table
8 + 32*njoints. 0 means there are no more
segments. (Put at very end of table)
TIME milliseconds for this segment
TRANS ptr to list of transforms+valid. nos:
L-O Level-offset of first trans (or scalar)
VAL Place for validity number
MECH Mechanism number:
YARM_MECH, YHAND_MECH, BARM_MECH or BHAND_MECH
L-O Level-offset for next variable
:
MECH Last mechanism number
CODE ptr to code to be scheduled at end
of this segment
A0 coeff (floating) first joint
:
A5 last coeff, first joint
A0 first coeff, second jolint
:
:
:
A5 last coeff, last joint
NCI final joint gravity loading, first joint
NCII final joint inertia loading, first joint
:
:
NCI final joint gravity loading, last joint
NCII final joint inertia loading, last joint
RELATIVE SEGMENT PTR
This is the format for the transform-validity list:
T1 level-offset of transform for first arm
0 room for validity number
:
Tn level-offset of transform for last arm
0 room for validity number
DATA and RELOC are used to output the motion table. Each of
these is 1000 long (very long motions may not fit.). See
EMITER.HDR for the pseudo-op definitions.
;
ARRCLR(RELOC);
ARRCLR(DATA);
! Output pseudo-op for motion;
LAB ← GENLABEL; ! Points to motion table;
EMIT(PSDCODE,MOVE_PSOP,PSINST);
EMIT(PSDCODE,LAB,SYMREF);
EMIT(PSDCODE,ARM,CONST);
! EMIT(PSDCODE,ERROR_BITS,CONST) - in PASS3 - bit mask for error handler;
! EMIT(PSDCODE,LAB2,SYMREF) - in PASS3 - tells us where to go for next pcode;
! EMIT(PSDCODE,LAB1,SYMREF) - in PASS3 - tells us where to go for a retry;
! Output trajectory file;
MAKE_REMARK(TJFILE,"Motion table");
EMIT(TJFILE,LAB,SYMDEC);
! Check for nulling; ! No_nulling is once again the default;
P ← MOVE$:CLAUSES[MOV];
WHILE (P≠RNULL) ∧ (RECTYPE(CELL:CAR[P])≠LOC(NNULL)) DO P ← CELL:CDR[P];
IF (P=RNULL) ∨ (NNULL:FLAG[CELL:CAR[P]]) THEN DATA[6] ← NONULLCB;
! Check for run-time move;
P ← MOVE$:CLAUSES[MOV];
WHILE (P≠RNULL) ∧ (RECTYPE(CELL:CAR[P])≠LOC(RTMOVE)) DO P ← CELL:CDR[P];
IF (P≠RNULL) THEN DATA[6] ← DATA[6] LOR RTMOVECB;
! Check for wobble;
P ← MOVE$:CLAUSES[MOV];
WHILE (P≠RNULL) ∧ (RECTYPE(CELL:CAR[P])≠LOC(WOBBLE)) DO P ← CELL:CDR[P];
DATA[4] ← SBITS; ! servo bits;
RELOC[4] ← CONST;
DATA[5] ← 0; ! No second servo bits;
DATA[6] ← ! want DEPARTCB only if this motion has a deproach;
DATA[6] LOR ! this already contains the desired nulling bit;
(IF TTHREAD:MODE[TTHREAD:NEXT[MOTION]] LAND DEPA_MODE THEN DEPARTCB ELSE 0)
LOR (IF P≠RNULL THEN WOBBLECB ELSE 0);
RELOC[5] ← RELOC[6] ← CONST;
EMIT(TJFILE,DATA[4],RELOC[4],3); ! Label, servo bits, servo bits, command bits;
IF P≠RNULL THEN ! Emit pointer to wobble value & the actual value;
BEGIN
EMIT(TJFILE,LAB←GENLABEL,SYMREF,1); ! Point to the wobble value;
MAKE_REMARK(SMLBLK,"Wobble value for motion");
EMIT(SMLBLK,LAB,SYMDEC,1); ! Here is the wobble value;
DATA[7] ← MEMORY[LOC(SVAL:VAL[WOBBLE:VAL[CELL:CAR[P]]]),INTEGER];
RELOC[7] ← FLOAT;
EMIT(SMLBLK,DATA[7],RELOC[7],1);
END
ELSE EMIT(TJFILE,0,CONST,1); ! The absence of a wobble value;
SEGLEN ← 8 + 32*(IF (ARM = YARM_MECH) OR (ARM = BARM_MECH) THEN 6 ELSE 1);
DATA[7] ← SEGLEN; RELOC[7] ← CONST;
Q ← TTHREAD:NEXT[MOTION];
WHILE Q ≠ RNULL DO
BEGIN "coeout" ! Coefficients for one segment;
INTEGER PLACETYPE; ! One of EXPRN, VALU$, VARIABLE;
P ← Q;
Q ← TTHREAD:NEXT[Q];
IF TTHREAD:PLACE[P] = RNULL THEN CONTINUE "coeout";
! Avoid outputting the short segments that end at
unconstrained points;
MAKE_REMARK(TJFILE,"Relative segment pointer");
DATA[8] ← TTHREAD:STIME[P]*1000.; RELOC[8] ← CONST; ! Milliseconds;
EMIT(TJFILE,DATA[7],RELOC[7],2); ! Relative seg ptr, time;
PLACETYPE ← RECTYPE(DEXPR:EXPN[TTHREAD:PLACE[P]]);
IF PLACETYPE=LOC(VARIABLE) OR PLACETYPE=LOC(EXPRN)
THEN BEGIN "needtrans"
! There is an associated place, need a trans pointer;
EMIT(TJFILE,LAB←GENLABEL,SYMREF,1); ! Refer to the trans pointer;
MAKE_REMARK(SMLBLK,"Trans pointer for motion");
EMIT(SMLBLK,LAB,SYMDEC,1); ! Here is the trans pointer;
EMIT(SMLBLK,VARIABLE:OFFSET[DEXPR:VAR[TTHREAD:PLACE[P]]],CONST,1);
! Point to the trans itself;
! Currently only one frame allowed;
EMIT(SMLBLK,0,CONST,1); ! Leave room for the validity bit;
END "needtrans"
ELSE EMIT(TJFILE,0,CONST,1); ! The absence of a trans pointer;
IF TTHREAD:EVENT[P]≠RNULL
THEN BEGIN ! There is some associated code, need to signal to it.;
EMIT(TJFILE,LAB←GENLABEL,SYMREF,1); ! Refer to the event pointer;
MAKE_REMARK(SMLBLK,"Event pointer for VIA code");
EMIT(SMLBLK,LAB,SYMDEC,1); ! Here is the event pointer;
EMIT(SMLBLK,VARIABLE:OFFSET[TTHREAD:EVENT[P]],CONST,1);
! Point to the event itself;
EMIT(SMLBLK,0,CONST,1); ! Leave room for the armcode to use;
END
ELSE
EMIT(TJFILE,0,CONST,1); ! The absence of associated code;
DPTR ← 9;
MAKE_REMARK(TJFILE,"Coefficients, gravity, inertia");
! Coefficients;
FOR JOINT← LOJOINT STEP 1 UNTIL HIJOINT DO
BEGIN ! Each iteration spits out the coefficient of one joint;
INTEGER DEGR;
FOR DEGR ← 0 STEP 1 UNTIL 5 DO
DATA[DPTR+DEGR] ← MEMORY[LOC(TTHREAD:COEFF[P][JOINT,DEGR]),INTEGER];
DPTR ← DPTR + 6;
END;
! The gravity and inertia terms;
FOR I ← 2*LOJOINT STEP 1 UNTIL 2*HIJOINT+1 DO
BEGIN ! Each 2 iterations spits out the terms for one joint;
DATA[DPTR] ← MEMORY[LOC(TTHREAD:GRAVIN[P][I]),INTEGER];
DPTR ← DPTR+1;
END;
RELOC[9] ← FLOAT; ! They are all floating point constants;
ARRBLT(RELOC[10],RELOC[9],DPTR-10);
EMIT(TJFILE,DATA[9],RELOC[9],DPTR-9); ! All the coefficients for this seg;
END "coeout";
EMIT(TJFILE,0,CONST,1); ! The last relative segment pointer is 0;
MAKE_REMARK(TJFILE,"End of motion table");
! Reclaim all the arrays in the motion thread;
Q ← MOTION;
WHILE Q ≠ RNULL DO
BEGIN "reclaim"
INTEGER ADR;
EXTERNAL PROCEDURE ARYEL (INTEGER ADRESS); ! In the SAIL segment;
ADR ← 0;
MEMLOC(TTHREAD:ANGLES[Q],INTEGER) ↔ ADR;
IF ADR THEN ARYEL(ADR);
ADR ← 0;
MEMLOC(TTHREAD:VELS[Q],INTEGER) ↔ ADR;
IF ADR THEN ARYEL(ADR);
ADR ← 0;
MEMLOC(TTHREAD:COEFF[Q],INTEGER) ↔ ADR;
IF ADR THEN ARYEL(ADR);
ADR ← 0;
MEMLOC(TTHREAD:GRAVIN[Q],INTEGER) ↔ ADR;
IF ADR THEN ARYEL(ADR);
Q ← TTHREAD:NEXT[Q];
END "reclaim";
! End of TRJCLC;
END "trjclc";
! CENTCLC, STOPCLC;
INTERNAL PROCEDURE CENTCLC (RPTR(CENTER) CNTR);
BEGIN "centclc"
! The "trajectory" table looks like this:
COFLST: XXXXXX TWO SERVO BIT WORDS, 7 BITS MUST BE ON, A HAND
XXXXXX SERVO AND ALL JOINT SERVOS OF THE SAME ARM
0 NO COMMAND BITS
0 NO WOBBLE VALUE
0 NO NEXT SEGMENT
0 NO FUNCTION TIME
0 NO TRANSFORM
CODE PTR TO CODE TO BE SCHEDULED THIS SEG
NO POLYNOMIAL TO FOLLOW
;
! Does not yet handle any cmons or code;
INTEGER ARM, SBITS, LAB;
INTEGER LOJOINT, HIJOINT; ! Not used;
PRELOAD_WITH 0,0,0,0,0,0;
OWN INTEGER ARRAY ZEROS[1:6];
PRELOAD_WITH CONST,CONST,CONST,CONST,CONST,CONST;
OWN INTEGER ARRAY CONSTS[1:6];
DEVBITS(ARM,SBITS,LOJOINT,HIJOINT,CENTER:CF[CNTR]);
! Want to turn on the hand bits as well:
IF ARM = YARM_MECH
THEN BEGIN
ARM ← YARM_MECH + YHAND_MECH
SBITS ← YARMSB + YHANDSB
END
ELSE IF ARM = BARM_MECH
THEN BEGIN
ARM ← BARM_MECH + BHAND_MECH
SBITS ← BARMSB + BHANDSB
END;
! Do it like this:;
ARM ← ARM LOR (ARM LSH 1);
SBITS ← SBITS LOR (SBITS LSH -1);
EMIT(PSDCODE,CENTER_PSOP,PSINST);
LAB ← GENLABEL;
EMIT(PSDCODE,LAB,SYMREF);
EMIT(PSDCODE,ARM,CONST);
! EMIT(PSDCODE,LAB1,SYMREF) - in PASS3 - tells us where to go for a retry;
MAKE_REMARK(TJFILE,"Center table");
EMIT(TJFILE,LAB,SYMDEC);
EMIT(TJFILE,SBITS,CONST);
EMIT(TJFILE,ZEROS[1],CONSTS[1],6);
END "centclc";
INTERNAL PROCEDURE STOPCLC(RPTR(STOP) STP);
BEGIN "stopclc"
INTEGER ARM, SBITS, LOJOINT, HIJOINT; ! Only ARM is used;
DEVBITS(ARM,SBITS,LOJOINT, HIJOINT, STOP:CF[STP]);
EMIT(PSDCODE,STOP_PSOP,PSINST);
EMIT(PSDCODE,ARM,CONST);
END "stopclc";
END $$prgid;
! Bugs
;